/*
   Copyright 2007 Marco Slot (marco.slot@gmail.com)

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package nl.marcoslot.lib.calc;

/**
 * This class represents an operator in mathematical notation.
 *
 * @author Marco Slot
 */
public class Operator implements Comparable {
    
    public static final int   
            ADD       = 0, 
            MULTIPLY  = 1,
            SUBSTRACT = 2,
            DIVIDE    = 3;
    
    public static final int[] SYMBOL = new int[] {'+', '*', '-', '/'};
    
    private int operator;
    
    /**
     * Constructs a new Operater representing an mathematical operator.
     *
     * @param operatorSymbol
     *    Character that represents the operator
     * @throws nl.marcoslot.lib.calc.Operator.UnknownOperatorException
     *    Thrown when the operator symbol is unknown
     */
    public Operator(char operatorSymbol) throws UnknownOperatorException {
        this(getOperatorBySymbol(operatorSymbol));
    }
    
    /**
     * Constructs a new Operater representing a mathematical operator.
     *
     * @param operator
     *    Operator number (ADD, MULTIPLY, etc.)
     * @throws nl.marcoslot.lib.calc.Operator.UnknownOperatorException
     *    Thrown when the operator symbol is unknown
     */
    public Operator(int operator) throws UnknownOperatorException {
        if(!isOperator(operator)) {
            throw new UnknownOperatorException();
        }
        this.operator = operator;
    }
    
    /**
     * Calculates and returns the outcome of v1 [operator] v2.
     *
     * @param v1
     *      The first operand
     * @param v2
     *      The second operand
     * @return
     *      The outcome of the calculation
     */
    public double calculate(double v1, double v2) {
        switch(operator) {
            case ADD:
                return v1 + v2;
            case SUBSTRACT:
                return v1 - v2;
            case MULTIPLY:
                return v1 * v2;
            case DIVIDE:
                return v1 / v2;
//            case MODULO:
//                return v1 % v2;
        }
        /* impossible */
        throw new Error("Unknown Operator.");
    }
    
    /**
     * Returns the operator value for a character
     *
     * @param c
     *      The operator symbol ('+', '-', '*', '/')
     * @return
     *      The value of the operator (ADD, SUBSTRACT, MULTIPLY DIVIDE)
     *     or -1 if the sybmol is unknown.
     */
    public static int getOperatorBySymbol(char c) {
        for(int i = 0; i < SYMBOL.length; i++) {
            if(SYMBOL[i] == c) {
                return i;
            }
        }
        return -1;
    }
    
    /**
     * Returns true if operatorSymbol represents an operator.
     *
     * @param operatorSymbol
     *      An operator symbol
     * @return
     *      true if operatorSymbol is a valid operator symbol; false otherwise
     */
    public static boolean isOperator(char operatorSymbol) {
        return isOperator(getOperatorBySymbol(operatorSymbol));
    }
    
    /**
     * Returns true if operator represents an operator
     *
     * @param operator
     *      An operator value
     * @return
     *      true if operator is a valid operator; false otherwise
     */
    public static boolean isOperator(int operator) {
        return operator >= 0 && operator < SYMBOL.length;
    }
    
    private static final boolean[] ASSOCIATIVE       = new boolean[] { true,  true, false, false};
    private static final boolean[] LEFT_ASSOCIATIVE  = new boolean[] {false, false,  true, true};
    private static final boolean[] RIGHT_ASSOCIATIVE = new boolean[] {false, false, false, false};
    
    /**
     * Returns true if the operator is associative
     *
     * @return
     *      true if operator is associative; false otherwise
     */
    public boolean isAssociative() {
        return ASSOCIATIVE[operator];
    }
    
    /**
     * Returns true if the operator is left-associative
     *
     * @return
     *      true if operator is left-associative; false otherwise
     */
    public boolean isLeftAssociative() {
        return LEFT_ASSOCIATIVE[operator];
    }
    
    /**
     * Returns true if the operator is right-associative
     *
     * @return
     *      true if operator is right-associative; false otherwise
     */
    public boolean isRightAssociative() {
        return RIGHT_ASSOCIATIVE[operator];
    }
    
    /**
     * Compares this object with the specified object for order.  Returns a
     * negative integer, zero, or a positive integer as this object is less
     * than, equal to, or greater than the specified object.<p>
     *
     * @param o the Object to be compared.
     * @return a negative integer, zero, or a positive integer as this operator
     * 		has less, equal, or greater precedence than the specified operator.
     * @throws ClassCastException if the specified object's type prevents it
     *         from being compared to this Object.
     */
    public int compareTo(Object o) {
        if(!(o instanceof Operator)) {
            return -1;
        }
        Operator other = (Operator) o;
        
        return (this.operator % 2) - (other.operator % 2);
    }
    
    
    /**
     * Exception thrown when using an unknown operator.
     *
     * @author Marco Slot
     */
    public class UnknownOperatorException extends RuntimeException {}
}

